use std::{
    borrow::Cow,
    future::Future,
    marker::PhantomData,
    mem,
    ops::{Deref, DerefMut},
    task::{ready, Poll},
};

use actix_web::{
    body::{BodySize, BoxBody, MessageBody}, dev::Payload, http::header::ContentType, web::{BufMut, Bytes, BytesMut}, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError
};
use futures_util::StreamExt;
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use crate::error::{new_err, AppErr};

const MAX_REQ_SIZE: usize = 20 * 1024 * 1024;

const RESP_HEAD: &'static str = "resp";
const RESP_OK: &'static str = "ok";
const RESP_FAIL: &'static str = "fail";
const RESP_SERIAL_FAIL: &'static str = "serial_fail";

#[derive(Debug, Deserialize, Serialize)]
pub struct Cbor<T>(pub T);

pub type CborRet<T> = Result<Cbor<T>, AppErr>;

pub fn new_ok<T>(v: T) -> CborRet<T> {
    Ok(Cbor(v))
}

impl<T> Deref for Cbor<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> DerefMut for Cbor<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

pub struct CborFut<T> {
    buf: BytesMut,
    payload: Payload,
    _v: PhantomData<T>,
}

impl<T: DeserializeOwned + Unpin> FromRequest for Cbor<T> {
    type Error = AppErr;
    type Future = CborFut<T>;

    fn from_request(_req: &actix_web::HttpRequest, payload: &mut Payload) -> Self::Future {
        CborFut {
            buf: BytesMut::new(),
            payload: payload.take(),
            _v: PhantomData,
        }
    }
}

impl<T: DeserializeOwned + Unpin> Future for CborFut<T> {
    type Output = Result<Cbor<T>, AppErr>;

    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
        let this = self.get_mut();
        let item = ready!(this.payload.poll_next_unpin(cx));
        match item {
            None => {
                let body = serde_cbor::from_slice::<T>(&this.buf);
                match body {
                    Ok(body) => return Poll::Ready(Ok(Cbor(body))),
                    Err(_) => return Poll::Ready(Err(new_err("parse cbor fail"))),
                }
            }
            Some(Ok(buf)) => {
                if (buf.len() + this.buf.len()) > MAX_REQ_SIZE {
                    return Poll::Ready(Err(new_err("req size to large")));
                } else {
                    this.buf.put_slice(&buf);
                    return Poll::Pending;
                }
            }
            Some(Err(_)) => {
                return Poll::Ready(Err(new_err("payload err")));
            }
        };
    }
}

#[derive(Debug, Serialize)]
struct ErrResp {
    err_code: i32,
    err_msg: Cow<'static, str>,
}

impl ResponseError for AppErr {
    fn error_response(&self) -> HttpResponse<BoxBody> {
        let body = match self {
            Self::Service(resp) => serde_cbor::to_vec(resp),
            _ => {
                let resp = ErrResp {
                    err_code: -1,
                    err_msg: Cow::Owned(self.to_string()),
                };
                serde_cbor::to_vec(&resp)
            }
        };

        match body {
            Ok(body) => HttpResponse::Ok()
                .content_type(ContentType::octet_stream())
                .insert_header((RESP_HEAD, RESP_FAIL))
                .body(body),
            Err(e) => HttpResponse::Ok()
                .content_type(ContentType::octet_stream())
                .insert_header((RESP_HEAD, RESP_SERIAL_FAIL))
                .body(e.to_string()),
        }
    }
}

#[derive(Debug, Default)]
pub struct RawBuf(pub Box<[u8]>);

impl MessageBody for RawBuf {
    type Error = AppErr;

    fn size(&self) -> BodySize {
        BodySize::Sized(self.0.len() as u64)
    }

    fn try_into_bytes(self) -> Result<Bytes, Self>
    where
        Self: Sized,
    {
        Ok(Bytes::from(self.0))
    }

    fn poll_next(
        self: std::pin::Pin<&mut Self>,
        _cx: &mut std::task::Context<'_>,
    ) -> Poll<Option<Result<Bytes, Self::Error>>> {
        if self.0.is_empty() {
            Poll::Ready(None)
        } else {
            let buf = mem::take(self.get_mut());

            Poll::Ready(Some(Ok(buf.0.into())))
        }
    }
}

impl<T: Serialize> Responder for Cbor<T> {
    type Body = BoxBody;

    fn respond_to(self, _req: &actix_web::HttpRequest) -> HttpResponse {
        let body = serde_cbor::to_vec(&self.0);
        match body {
            Ok(body) => HttpResponse::Ok()
                .content_type(ContentType::octet_stream())
                .insert_header((RESP_HEAD, RESP_OK))
                .body(body),
            Err(e) => HttpResponse::Ok()
                .content_type(ContentType::octet_stream())
                .insert_header((RESP_HEAD, RESP_SERIAL_FAIL))
                .body(e.to_string()),
        }
    }
}

impl Responder for RawBuf {

    type Body = BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
        HttpResponse::Ok()
        .insert_header((RESP_HEAD, RESP_OK))
        .body(self)
    }
}

pub struct Download {
    body: Box<[u8]>,
    name: String,
}

impl Download {
    pub fn new(name: String, body: Box<[u8]>) -> Self {
        Self { body, name }
    }
}

impl Responder for Download {
    type Body = BoxBody;

    fn respond_to(self, _req: &actix_web::HttpRequest) -> HttpResponse {
        let v = format!("attachment;filename={}", self.name);
        HttpResponse::Ok()
            .content_type(ContentType::octet_stream())
            .insert_header(("Content-Disposition", v))
            .insert_header((RESP_HEAD, RESP_OK))
            .body( RawBuf(self.body) )
    }
}

pub struct Void;

impl Responder for Void {
    type Body = BoxBody;

    fn respond_to(self, _req: &actix_web::HttpRequest) -> HttpResponse {
        HttpResponse::Ok()
            .insert_header((RESP_HEAD, RESP_OK))
            .finish()
    }
}
